<!DOCTYPE html>



  


<html class="theme-next mist use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">
















  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">







<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="Hexo, NexT">










<meta name="description" content="为什么使用分布式事物？  Service 产生多个节点，一个小的服务单元本事多节点部署 。 Resource 产生多个节点：数据库等不在一个地方，不同的服务如果保证一致 保证数据的可用性，一致性，完整性。（转账的例子）  分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。本质上来说，分布式事务就是为了保证不同数据库的数据一致性。">
<meta property="og:type" content="article">
<meta property="og:title" content="分布式事务">
<meta property="og:url" content="http://changyuan.github.io/2019/01/30/distributed-transaction/index.html">
<meta property="og:site_name" content="Chang Crazy&#39;s Blog">
<meta property="og:description" content="为什么使用分布式事物？  Service 产生多个节点，一个小的服务单元本事多节点部署 。 Resource 产生多个节点：数据库等不在一个地方，不同的服务如果保证一致 保证数据的可用性，一致性，完整性。（转账的例子）  分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。本质上来说，分布式事务就是为了保证不同数据库的数据一致性。">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://wx3.sinaimg.cn/mw690/6972aca8ly1fzimzin6ffj20lz0e8wg8.jpg">
<meta property="og:image" content="https://wx4.sinaimg.cn/mw690/6972aca8ly1fz8gszyj0lj20ns0bldhs.jpg">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/11349666-abdeca2a04207329.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/603/format/webp">
<meta property="og:image" content="https://upload-images.jianshu.io/upload_images/11349666-8211b5a685e7081e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/598/format/webp">
<meta property="og:image" content="https://wx4.sinaimg.cn/mw690/6972aca8ly1fz6d7kc4xej20u009974u.jpg">
<meta property="og:image" content="http://docs-aliyun.cn-hangzhou.oss.aliyun-inc.com/assets/pic/48726/cn_zh/1498619797175/GTS%20%E6%9E%B6%E6%9E%84.png">
<meta property="og:updated_time" content="2019-01-30T08:26:58.000Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="分布式事务">
<meta name="twitter:description" content="为什么使用分布式事物？  Service 产生多个节点，一个小的服务单元本事多节点部署 。 Resource 产生多个节点：数据库等不在一个地方，不同的服务如果保证一致 保证数据的可用性，一致性，完整性。（转账的例子）  分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。本质上来说，分布式事务就是为了保证不同数据库的数据一致性。">
<meta name="twitter:image" content="https://wx3.sinaimg.cn/mw690/6972aca8ly1fzimzin6ffj20lz0e8wg8.jpg">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Mist',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="http://changyuan.github.io/2019/01/30/distributed-transaction/">





  <title>分布式事务 | Chang Crazy's Blog</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">Chang Crazy's Blog</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">升级自己的操作系统</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-schedule">
          <a href="/schedule/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-calendar"></i> <br>
            
            日程表
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>
            
            关于
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br>
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off" placeholder="搜索..." spellcheck="false" type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="http://changyuan.github.io/2019/01/30/distributed-transaction/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="changyuan">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="Chang Crazy's Blog">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">分布式事务</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-01-30T16:26:58+08:00">
                2019-01-30
              </time>
            

            

            
          </span>

          

          
            
          

          
          

          
            <span class="post-meta-divider">|</span>
            <span class="page-pv"><i class="fa fa-file-o"></i>
            <span class="busuanzi-value" id="busuanzi_value_page_pv"></span>
            </span>
          

          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <p>为什么使用分布式事物？</p>
<ol>
<li>Service 产生多个节点，一个小的服务单元本事多节点部署 。</li>
<li>Resource 产生多个节点：数据库等不在一个地方，不同的服务如果保证一致</li>
<li>保证数据的可用性，一致性，完整性。（转账的例子）</li>
</ol>
<p>分布式事务指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。<br><strong>本质上来说，分布式事务就是为了保证不同数据库的数据一致性。</strong></p>
<a id="more"></a>
<h2 id="概念介绍"><a href="#概念介绍" class="headerlink" title="概念介绍"></a>概念介绍</h2><h3 id="CAP理论"><a href="#CAP理论" class="headerlink" title="CAP理论"></a>CAP理论</h3><p>分布式事物CAP理论：</p>
<ul>
<li>Consistency(一致性), 数据一致更新，所有数据变动都是同步的</li>
<li>Availability(可用性), 好的响应性能</li>
<li>Partition tolerance(分区容错性) 可靠性，部分节点出现问题，其他节点仍然可用。<blockquote>
<p>定理：任何分布式系统只可同时满足二点，没法三者兼顾。<br>忠告：架构师不要将精力浪费在如何设计能满足三者的完美分布式系统，而是应该进行取舍。</p>
</blockquote>
</li>
</ul>
<p>分布式事物保持最终一致性。</p>
<p>对于 CP 来说，放弃可用性，追求一致性和分区容错性，我们的 ZooKeeper 其实就是追求的强一致。<br>对于 AP 来说，放弃一致性(这里说的一致性是强一致性)，追求分区容错性和可用性，这是很多分布式系统设计时的选择，后面的 BASE 也是根据 AP 来扩展。</p>
<p>基于分布式领域BASE理论，它是在CAP理论（一致性、可用性、分区容忍性）的基础之上的延伸。</p>
<h3 id="BASE理论"><a href="#BASE理论" class="headerlink" title="BASE理论"></a>BASE理论</h3><p>BASE 是 Basically Available(基本可用)、Soft state(软状态)和 Eventually consistent (最终一致性)三个短语的缩写，是对 CAP 中 AP 的一个扩展。<br>同时 CAP 中选择两个，比如你选择了 CP，并不是叫你放弃 A。因为 P 出现的概率实在是太小了，大部分的时间你仍然需要保证 CA。</p>
<blockquote>
<p>基本可用：分布式系统出现故障的时候，允许损失一部分可用性,保证核心功能可用。</p>
</blockquote>
<blockquote>
<p>柔性状态：允许系统存在中间状态，这个中间状态又不会影响系统整体可用性。</p>
</blockquote>
<blockquote>
<p>最终一致性：数据经过重试等机制处理后，最终数据能达到一致。</p>
</blockquote>
<p>ACID是传统数据库常用的设计思想，它追求的是强一致性。BASE是大型分布式系统场景下的设计思想，通过牺牲强一致性获得高可用性。</p>
<h2 id="实现方式"><a href="#实现方式" class="headerlink" title="实现方式"></a>实现方式</h2><h3 id="2PC（全局事务型）"><a href="#2PC（全局事务型）" class="headerlink" title="2PC（全局事务型）"></a>2PC（全局事务型）</h3><p>数据库支持的 XA Transactions 事物机制，Mysql5.5以上是否支持。推荐mariadb<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SHOW VARIABLES LIKE &apos;%innodb_support_xa%&apos;;</span><br></pre></td></tr></table></figure></p>
<p>资源管理器（resource manager）：用来管理系统资源，是通向事务资源的途径。数据库就是一种资源管理器。资源管理还应该具有管理事务提交或回滚的能力。</p>
<p>事务管理器（transaction manager）：事务管理器是分布式事务的核心管理者。事务管理器与每个资源管理器（resource<br>manager）进行通信，协调并完成事务的处理。事务的各个分支由唯一命名进行标识。</p>
<p>XA 是一个两阶段提交协议:</p>
<ol>
<li>预处理阶段：事务协调器要求每个涉及到事务的数据库预提交(precommit)此操作，并反映是否可以提交</li>
<li>提交阶段：事务协调器要求每个数据库提交数据。</li>
</ol>
<p><img src="https://wx3.sinaimg.cn/mw690/6972aca8ly1fzimzin6ffj20lz0e8wg8.jpg" alt="image"></p>
<p>MYSQL对XA事物支持过程：<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line">XA &#123;START|BEGIN&#125; xid [JOIN|RESUME]   //开启XA事务，如果使用的是XA START而不是XA</span><br><span class="line"></span><br><span class="line">BEGIN，那么不支持[JOIN|RESUME]，xid是一个唯一值，表示事务分支标识符</span><br><span class="line"></span><br><span class="line">XA END xid [SUSPEND [FOR MIGRATE]]   //结束一个XA事务，不支持[SUSPEND [FOR MIGRATE]]</span><br><span class="line"></span><br><span class="line">XA PREPARE xid 准备提交</span><br><span class="line"></span><br><span class="line">XA COMMIT xid [ONE PHASE] //提交，如果使用了ONE PHASE，则表示使用一阶段提交。两阶段提交协议中，如果只有一个RM参与，那么可以优化为一阶段提交</span><br><span class="line"></span><br><span class="line">XA ROLLBACK xid  //回滚</span><br><span class="line"></span><br><span class="line">XA RECOVER [CONVERT XID]  //列出所有处于PREPARE阶段的XA事务</span><br></pre></td></tr></table></figure></p>
<p>缺点：</p>
<p>1、同步阻塞问题。执行过程中，所有参与节点都是事务阻塞型的。当参与者占有公共资源时，其他第三方节点访问公共资源不得不处于阻塞状态。</p>
<p>2、单点故障。由于协调者的重要性，一旦协调者发生故障。参与者会一直阻塞下去。尤其在第二阶段，协调者发生故障，那么所有的参与者还都处于锁定事务资源的状态中，而无法继续完成事务操作。（如果是协调者挂掉，可以重新选举一个协调者，但是无法解决因为协调者宕机导致的参与者处于阻塞状态的问题）</p>
<p>3、数据不一致。在二阶段提交的阶段二中，当协调者向参与者发送commit请求之后，发生了局部网络异常或者在发送commit请求过程中协调者发生了故障，这回导致只有一部分参与者接受到了commit请求。而在这部分参与者接到commit请求之后就会执行commit操作。但是其他部分未接到commit请求的机器则无法执行事务提交。于是整个分布式系统便出现了数据部一致性的现象。</p>
<p>4、二阶段无法解决的问题：协调者再发出commit消息之后宕机，而唯一接收到这条消息的参与者同时也宕机了。那么即使协调者通过选举协议产生了新的协调者，这条事务的状态也是不确定的，没人知道事务是否被已经提交。</p>
<p>XA 协议比较简单，成本较低，但是其单点问题，以及不能支持高并发(由于同步阻塞)依然是其最大的弱点。</p>
<p>DEMO：</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br></pre></td><td class="code"><pre><span class="line">$pdo1 = DB::connection()-&gt;getPdo();</span><br><span class="line">$pdo2 = DB::connection(<span class="string">'jianzhi_vip'</span>)-&gt;getPdo();</span><br><span class="line"></span><br><span class="line">$xid = uniqid();</span><br><span class="line">$xid2 = uniqid();</span><br><span class="line"><span class="keyword">$this</span>-&gt;info(<span class="string">'开启xa事物'</span>);</span><br><span class="line"></span><br><span class="line">$pdo1-&gt;exec(<span class="string">"XA START '$xid'"</span>);</span><br><span class="line">$pdo2-&gt;exec(<span class="string">"XA START '$xid2'"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> &#123;</span><br><span class="line">    <span class="keyword">$this</span>-&gt;info(<span class="string">'准备xa事物'</span>);</span><br><span class="line">    $ret1 = $pdo1-&gt;exec(<span class="string">"UPDATE jz_abtest_record SET test_result=test_result+1 WHERE id=1"</span>);</span><br><span class="line"></span><br><span class="line">    $ret2 = $pdo2-&gt;exec(<span class="string">"UPDATE test SET age=7 WHERE id=1"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//阶段1：$dbtest1提交准备就绪</span></span><br><span class="line">    $pdo1-&gt;exec(<span class="string">"XA END '$xid'"</span>);</span><br><span class="line">    $pdo1-&gt;exec(<span class="string">"XA PREPARE '$xid'"</span>);</span><br><span class="line">    <span class="comment">//阶段1：$dbtest2提交准备就绪</span></span><br><span class="line">    $pdo2-&gt;exec(<span class="string">"XA END '$xid2'"</span>);</span><br><span class="line">    $pdo2-&gt;exec(<span class="string">"XA PREPARE '$xid2'"</span>);</span><br><span class="line"></span><br><span class="line">    <span class="comment">//异常报告写在prepare之后</span></span><br><span class="line">    <span class="keyword">if</span> (!$ret1 || !$ret2) &#123;</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> <span class="keyword">Exception</span>(<span class="string">"错误异常操作"</span>, <span class="number">1</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">$this</span>-&gt;info(<span class="string">'提交xa事物'</span>);</span><br><span class="line">    <span class="comment">//阶段2：提交两个库</span></span><br><span class="line">    $pdo1-&gt;exec(<span class="string">"XA COMMIT '$xid'"</span>);</span><br><span class="line">    $pdo2-&gt;exec(<span class="string">"XA COMMIT '$xid2'"</span>);</span><br><span class="line"></span><br><span class="line">&#125; <span class="keyword">catch</span> (<span class="keyword">Exception</span> $e) &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">$this</span>-&gt;info(<span class="string">'回滚'</span>);</span><br><span class="line">    <span class="comment">//阶段2：回滚</span></span><br><span class="line">    $pdo1-&gt;exec(<span class="string">"XA ROLLBACK '$xid'"</span>);</span><br><span class="line">    $pdo2-&gt;exec(<span class="string">"XA ROLLBACK '$xid2'"</span>);</span><br><span class="line">    print_r($e-&gt;getMessage());</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h3 id="TCC（事务补偿型）"><a href="#TCC（事务补偿型）" class="headerlink" title="TCC（事务补偿型）"></a>TCC（事务补偿型）</h3><p><img src="https://wx4.sinaimg.cn/mw690/6972aca8ly1fz8gszyj0lj20ns0bldhs.jpg" alt="TCC"></p>
<p>TCC 其实就是采用的补偿机制，其核心思想是：<strong>针对每个操作，都要注册一个与其对应的确认和补偿（撤销）操作</strong></p>
<p>它分为三个阶段：</p>
<ul>
<li>Try 阶段主要是对业务系统做检测及资源预留</li>
<li>Confirm 阶段主要是对业务系统做确认提交，Try阶段执行成功并开始执行 Confirm阶段时，默认Confirm阶段是不会出错的。即：只要Try成功，Confirm一定成功。</li>
<li>Cancel 阶段主要是在业务执行错误，需要回滚的状态下执行的业务取消，预留资源释放。</li>
</ul>
<blockquote>
<p>举个例子，假入 Bob 要向 Smith 转账，思路大概是：<br>我们有一个本地方法，里面依次调用<br>1、首先在 Try 阶段，要先调用远程接口把 Smith 和 Bob 的钱给冻结起来。<br>2、在 Confirm 阶段，执行远程调用的转账的操作，转账成功进行解冻。<br>3、如果第2步执行成功，那么转账成功，如果第二步执行失败，则调用远程冻结接口对应的解冻方法 (Cancel)。</p>
</blockquote>
<p>优点： 跟2PC比起来，实现以及流程相对简单了一些，但数据的一致性比2PC也要差一些,增加可用性</p>
<p>缺点： </p>
<ol>
<li>业务逻辑的每个分支都需要实现try、confirm、cancel三个操作，应用侵入性较强，改造成本高</li>
<li>实现难度较大。需要按照网络状态、系统故障等不同的失败原因实现不同的回滚策略。为了满足一致性的要求，confirm和cancel接口必须实现幂等</li>
</ol>
<p>TCC两阶段提交与XA两阶段提交的区别是：</p>
<ol>
<li>XA是资源层面的分布式事务，强一致性，在两阶段提交的整个过程中，一直会持有资源的锁。</li>
</ol>
<blockquote>
<p>XA事务中的两阶段提交内部过程是对开发者屏蔽的，回顾我们之前讲解JTA规范时，通过UserTransaction的commit方法来提交全局事务，这只是一次方法调用，其内部会委派给TransactionManager进行真正的两阶段提交，因此开发者从代码层面是感知不到这个过程的。而事务管理器在两阶段提交过程中，从prepare到commit/rollback过程中，资源实际上一直都是被加锁的。如果有其他人需要更新这两条记录，那么就必须等待锁释放。</p>
</blockquote>
<p>2.TCC是业务层面的分布式事务，最终一致性，不会一直持有资源的锁。</p>
<blockquote>
<p>TCC中的两阶段提交并没有对开发者完全屏蔽，也就是说从代码层面，开发者是可以感受到两阶段提交的存在。如上述航班预定案例：在第一阶段，航空公司需要提供try接口(机票资源预留)。在第二阶段，航空公司提需要提供confirm/cancel接口(确认购买机票/取消预留)。开发者明显的感知到了两阶段提交过程的存在。try、confirm/cancel在执行过程中，一般都会开启各自的本地事务，来保证方法内部业务逻辑的ACID特性。其中：try过程的本地事务，是保证资源预留的业务逻辑的正确性,confirm/cancel执行的本地事务逻辑确认/取消预留资源，以保证最终一致性.</p>
</blockquote>
<h3 id="异步确保最终一致型-柔性事务"><a href="#异步确保最终一致型-柔性事务" class="headerlink" title="异步确保最终一致型(柔性事务)"></a>异步确保最终一致型(柔性事务)</h3><p>它的核心思想是将需要分布式处理的<strong>任务通过消息</strong>或者<strong>日志的方式来异步执行</strong>，消息或日志可以存到本地文件、数据库或消息队列，再通过业务规则进行失败重试，它要求各服务的接口是幂等的。</p>
<ol>
<li>记录日志+补偿<br>记录事务的开始和结束状态。事务根据日志记录找回事务的当前执行状态，并根据状态决定重试异常步骤，也就是正向补偿，或者回滚上一次执行步骤，也就是反向补偿。</li>
<li>消息<br>多次重试，也就是发送多次消息，由于要多次重发，所以程序必须是<strong>幂等</strong>（同一操作反复执行多次结果不变）</li>
<li>无锁设计<br>放弃锁是一个解决问题的思路。比如通过乐观锁，大多数是基于版本号来实现。</li>
</ol>
<h4 id="本地消息表（异步确保型）"><a href="#本地消息表（异步确保型）" class="headerlink" title="本地消息表（异步确保型）"></a>本地消息表（异步确保型）</h4><p>其核心思想是将分布式事务拆分成本地事务进行处理:<br><img src="https://upload-images.jianshu.io/upload_images/11349666-abdeca2a04207329.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/603/format/webp" alt="消息表"></p>
<ol>
<li><p>消息生产方，需要额外建一个消息表，并记录消息发送状态。消息表和业务数据要在一个事务里提交，也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败，会进行重试发送。</p>
</li>
<li><p>消息消费方，需要处理这个消息，并完成自己的业务逻辑。此时如果本地事务处理成功，表明已经处理成功了，如果处理失败，那么就会重试执行。如果是业务上面的失败，可以给生产方发送一个业务补偿消息，通知生产方进行回滚等操作。</p>
</li>
<li><p>生产方和消费方定时扫描本地消息表，把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑，这种方案还是非常实用的。</p>
</li>
</ol>
<p>这种方案遵循BASE理论，采用的是最终一致性，笔者认为是这几种方案里面比较适合实际业务场景的，即不会出现像2PC那样复杂的实现(当调用链很长的时候，2PC的可用性是非常低的)，也不会像TCC那样可能出现确认或者回滚不了的情况。</p>
<p>举个经典的跨行转账的例子来描述。<br>第一步伪代码如下，扣款100，通过本地事务保证了凭证消息插入到消息表中：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">begin transaction:</span><br><span class="line">　　update User set account = account - 100 where userId = &apos;A&apos;</span><br><span class="line">　　insert into message(msgId, userId, amount, status) values(&apos;123&apos;,&apos;A&apos;, 100, 1)</span><br><span class="line">commit transaction</span><br></pre></td></tr></table></figure>
<p>第二步，通知对方银行账户上加100了。那问题来了，如何通知到对方呢？</p>
<p>通常采用两种方式：</p>
<ol>
<li>采用时效性高的MQ，由对方订阅消息并监听，有消息时自动触发事件。</li>
<li>采用定时轮询扫描的方式，去检查消息表的数据。<br>两种方式其实各有利弊，仅仅依靠MQ，可能会出现通知失败的问题。而过于频繁的定时轮询，效率也不是最佳的（90%是无用功）。所以，我们一般会把两种方式结合起来使用。</li>
</ol>
<p>解决了通知的问题，又有新的问题了。万一这消息有重复被消费，往用户帐号上多加了钱，那岂不是后果很严重？其实我们可以消息消费方也通过一个“消费状态表”来记录消费状态。在执行“加款”操作之前，检测下该消息（提供标识）是否已经消费过，消费完成后，通过本地事务控制来更新这个“消费状态表”。这样子就避免重复消费的问题：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">get msgId = &apos;123&apos;;</span><br><span class="line">check if mgsId is in message_applied(msgId);</span><br><span class="line">if not applied:</span><br><span class="line">    begin transaction:</span><br><span class="line">        update User set account = account + 100 where userId = &apos;B&apos;</span><br><span class="line">        insert into message_applied(msgId) values(&apos;123&apos;)</span><br><span class="line">    commit transaction</span><br></pre></td></tr></table></figure>
<p>上诉的方式是一种非常经典的实现，基本避免了分布式事务，实现了“最终一致性”。但是，关系型数据库的吞吐量和性能方面存在瓶颈，频繁的读写消息会给数据库造成压力。所以，在真正的高并发场景下，该方案也会有瓶颈和限制的。</p>
<p>缺点：<br>消息表会耦合到业务系统中，如果没有封装好的解决方案，会有很多杂活需要处理</p>
<h4 id="MQ非事务类型"><a href="#MQ非事务类型" class="headerlink" title="MQ非事务类型"></a>MQ非事务类型</h4><p>在本地消息表基础之上，优化一个独立的消息服务改进:</p>
<p>基本思路是将本地操作和发送消息放在一个事务中，保证本地操作和消息发送要么两者都成功或者都失败。下游应用向消息系统订阅该消息，收到消息后执行相应操作。</p>
<ol>
<li>操作数据库成功，向 MQ 中投递消息也成功，皆大欢喜</li>
<li>操作数据库失败，不会向 MQ 中投递消息了</li>
<li>操作数据库成功，但是向 MQ 中投递消息时失败，向外抛出了异常，刚刚执行的更新数据库的操作将被回滚</li>
</ol>
<p>Q: 如何保证消息与业务操作一致，不丢失？ </p>
<p>A: 主流的 MQ 产品都具有持久化消息的功能 + 重试机制。</p>
<p>Q: 如何避免消息被重复消费造成的问题？</p>
<ol>
<li>保证消费者调用业务的服务接口的幂等性。</li>
<li>通过消费日志或者类似状态表来记录消费状态，便于判断（建议在业务上自行实现，而不依赖MQ产品提供该特性）。</li>
</ol>
<h4 id="MQ事务类型"><a href="#MQ事务类型" class="headerlink" title="MQ事务类型"></a>MQ事务类型</h4><p>举个例子，Bob向Smith转账，那我们到底是先发送消息，还是先执行扣款操作？</p>
<p>好像都可能会出问题。如果先发消息，扣款操作失败，那么Smith的账户里面会多出一笔钱。反过来，如果先执行扣款操作，后发送消息，那有可能扣款成功了但是消息没发出去，Smith收不到钱。除了上面介绍的通过异常捕获和回滚的方式外，还有没有其他的思路呢？</p>
<p>利用RocketMQ 的事务消息优化上面的接口,利用事物消息取代上面独立的消息服务，发送Prepared消息之后，消费端是不能直接消费的，需要本地事物完成之后，确认了之后才行。</p>
<p><img src="https://upload-images.jianshu.io/upload_images/11349666-8211b5a685e7081e.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/598/format/webp" alt="image"></p>
<ol>
<li>第一阶段 Prepared 消息，会拿到消息的地址。</li>
<li>第二阶段执行本地事务。</li>
<li>第三阶段通过第一阶段拿到的地址去访问消息，并修改状态。</li>
</ol>
<p>消息接受者就能使用这个消息。如果确认消息失败，在 RocketMQ Broker中提供了定时扫描没有更新状态的消息。</p>
<p>如果有消息没有得到确认，会向消息发送者发送消息，来判断是否提交，在 RocketMQ 中是以 Listener 的形式给发送者，用来处理。</p>
<p>如果减了是回滚还是继续发送确认消息呢？RocketMQ 会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。</p>
<p>RocketMQ 事务设计结构(4.3以上)</p>
<p>事务消息作为一种异步确保型事务，  将两个事务分支通过 MQ 进行异步解耦，RocketMQ 2. 事务消息的设计流程同样借鉴了两阶段提交理论，整体交互流程如下图所示：<br><img src="https://wx4.sinaimg.cn/mw690/6972aca8ly1fz6d7kc4xej20u009974u.jpg" alt="image"></p>
<ol>
<li>事务发起方首先发送 prepare 消息到 MQ，RocketMQ将消息状态标记为Prepared，注意此时这条消息消费者是无法消费到的</li>
<li>在发送 prepare 消息成功后执行本地事务。</li>
<li>根据本地事务执行结果返回 commit 或者是 rollback。</li>
<li>如果消息是 rollback，MQ 将删除该 prepare 消息不进行下发，如果是 commit 消息，MQ 将会把这个消息发送给 consumer 端，RocketMQ将消息状态标记为可消费，这个时候消费者，才能真正的保证消费到这条数据。</li>
<li>如果执行本地事务过程中，执行端挂掉，或者超时，MQ 将会不停的询问其同组的其他 producer 来获取状态。</li>
<li><p>Consumer 端的消费成功机制有 MQ 保证。</p>
<p>如果确认消息发送失败了怎么办？</p>
<p>RocketMQ会定期扫描消息集群中的事务消息，如果发现了Prepared消息，它会向消息发送端(生产者)确认。RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。</p>
<p>如果消费失败怎么办？</p>
<p>阿里提供给我们的解决方法是：人工解决。</p>
</li>
</ol>
<h3 id="开源GTS全局事务中间件"><a href="#开源GTS全局事务中间件" class="headerlink" title="开源GTS全局事务中间件"></a>开源GTS全局事务中间件</h3><p><img src="http://docs-aliyun.cn-hangzhou.oss.aliyun-inc.com/assets/pic/48726/cn_zh/1498619797175/GTS%20%E6%9E%B6%E6%9E%84.png" alt="image"></p>
<h2 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h2><ul>
<li><a href="https://dev.mysql.com/doc/refman/8.0/en/xa-statements.html" target="_blank" rel="noopener">2pc XA 事务</a></li>
<li><a href="http://www.ywnds.com/?p=9049" target="_blank" rel="noopener">mysql XA 事务</a></li>
<li><a href="https://www.infoq.cn/article/solution-of-distributed-system-transaction-consistency" target="_blank" rel="noopener">分布式系统事务一致性</a></li>
<li><a href="https://www.jianshu.com/p/453c6e7ff81c" target="_blank" rel="noopener">RocketMQ 开放消息系统</a></li>
<li><a href="http://rocketmq.apache.org/docs/transaction-example/" target="_blank" rel="noopener">RocketMQ 分布式实现</a></li>
<li><a href="https://help.aliyun.com/document_detail/48726.html?spm=a2c4g.11174283.4.1.1cad735dnkjSxG" target="_blank" rel="noopener">GTS全局事务中间件</a></li>
<li><a href="http://www.tianshouzhi.com/api/tutorials/distributed_transaction/389" target="_blank" rel="noopener">事务消息可靠一致性</a></li>
</ul>

      
    </div>
    
    
    

    

    
      <div>
        <div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
  <div>分享即是成长</div>
  <button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
    <span>打赏</span>
  </button>
  <div id="QR" style="display: none;">

    
      <div id="wechat" style="display: inline-block">
        <img id="wechat_qr" src="/images/wechat-reward-image.png" alt="changyuan 微信支付">
        <p>微信支付</p>
      </div>
    

    

    

  </div>
</div>

      </div>
    

    
      <div>
        <ul class="post-copyright">
  <li class="post-copyright-author">
    <strong>本文作者：</strong>
    changyuan
  </li>
  <li class="post-copyright-link">
    <strong>本文链接：</strong>
    <a href="http://changyuan.github.io/2019/01/30/distributed-transaction/" title="分布式事务">http://changyuan.github.io/2019/01/30/distributed-transaction/</a>
  </li>
  <li class="post-copyright-license">
    <strong>版权声明： </strong>
    本博客所有文章除特别声明外，均采用 <a href="https://creativecommons.org/licenses/by-nc-sa/3.0/" rel="external nofollow" target="_blank">CC BY-NC-SA 3.0</a> 许可协议。转载请注明出处！
  </li>
</ul>

      </div>
    

    <footer class="post-footer">
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/2017/09/02/daohuo/" rel="next" title="盗火：硅谷、海豹突击队和疯狂科学家正在给我们的工作和生活带来一个革命">
                <i class="fa fa-chevron-left"></i> 盗火：硅谷、海豹突击队和疯狂科学家正在给我们的工作和生活带来一个革命
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/2019/01/30/elasticSearch/" rel="prev" title="Elastic Search">
                Elastic Search <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  



        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image" src="/images/avatar.jpg" alt="changyuan">
            
              <p class="site-author-name" itemprop="name">changyuan</p>
              <p class="site-description motion-element" itemprop="description">不断升级自己的操作系统</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">69</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">1</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">26</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a href="https://github.com/changyuan" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-fab fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://twitter.com/changocean1" target="_blank" title="Twitter">
                      
                        <i class="fa fa-fw fa-fab fa-twitter"></i>Twitter</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="http://weibo.com/changyuan2011" target="_blank" title="Weibo">
                      
                        <i class="fa fa-fw fa-fab fa-weibo"></i>Weibo</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a href="https://www.douban.com/people/changyuanyuan" target="_blank" title="Zhihu">
                      
                        <i class="fa fa-fw fa-fab fa-quora"></i>Zhihu</a>
                  </span>
                
            </div>
          

          
          

          
          
            <div class="links-of-blogroll motion-element links-of-blogroll-inline">
              <div class="links-of-blogroll-title">
                <i class="fa  fa-fw fa-link"></i>
                Links
              </div>
              <ul class="links-of-blogroll-list">
                
                  <li class="links-of-blogroll-item">
                    <a href="http://weibo.com/changyuan2011" title="Weibo" target="_blank">Weibo</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="https://github.com/changyuan" title="GitHub" target="_blank">GitHub</a>
                  </li>
                
              </ul>
            </div>
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-2"><a class="nav-link" href="#概念介绍"><span class="nav-number">1.</span> <span class="nav-text">概念介绍</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#CAP理论"><span class="nav-number">1.1.</span> <span class="nav-text">CAP理论</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#BASE理论"><span class="nav-number">1.2.</span> <span class="nav-text">BASE理论</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#实现方式"><span class="nav-number">2.</span> <span class="nav-text">实现方式</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#2PC（全局事务型）"><span class="nav-number">2.1.</span> <span class="nav-text">2PC（全局事务型）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#TCC（事务补偿型）"><span class="nav-number">2.2.</span> <span class="nav-text">TCC（事务补偿型）</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#异步确保最终一致型-柔性事务"><span class="nav-number">2.3.</span> <span class="nav-text">异步确保最终一致型(柔性事务)</span></a><ol class="nav-child"><li class="nav-item nav-level-4"><a class="nav-link" href="#本地消息表（异步确保型）"><span class="nav-number">2.3.1.</span> <span class="nav-text">本地消息表（异步确保型）</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#MQ非事务类型"><span class="nav-number">2.3.2.</span> <span class="nav-text">MQ非事务类型</span></a></li><li class="nav-item nav-level-4"><a class="nav-link" href="#MQ事务类型"><span class="nav-number">2.3.3.</span> <span class="nav-text">MQ事务类型</span></a></li></ol></li><li class="nav-item nav-level-3"><a class="nav-link" href="#开源GTS全局事务中间件"><span class="nav-number">2.4.</span> <span class="nav-text">开源GTS全局事务中间件</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#参考"><span class="nav-number">3.</span> <span class="nav-text">参考</span></a></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright">&copy; <span itemprop="copyrightYear">2023</span>
  <span class="with-love">
    <i class="fa fa-user"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">changyuan</span>

  
</div>









<!-- 网站运行时间的设置 -->
<span id="timeDate">载入天数...</span>
<span id="times">载入时分秒...</span>
<script>
    var now = new Date();
    function createtime() {
        var grt= new Date("08/08/2014 00:00:00");
        now.setTime(now.getTime()+250);
        days = (now - grt ) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
        hours = (now - grt ) / 1000 / 60 / 60 - (24 * dnum); hnum = Math.floor(hours);
        if(String(hnum).length ==1 ){hnum = "0" + hnum;} minutes = (now - grt ) / 1000 /60 - (24 * 60 * dnum) - (60 * hnum);
        mnum = Math.floor(minutes); if(String(mnum).length ==1 ){mnum = "0" + mnum;}
        seconds = (now - grt ) / 1000 - (24 * 60 * 60 * dnum) - (60 * 60 * hnum) - (60 * mnum);
        snum = Math.round(seconds); if(String(snum).length ==1 ){snum = "0" + snum;}
        document.getElementById("timeDate").innerHTML = "本站已运行 "+dnum+" 天 ";
        document.getElementById("times").innerHTML = hnum + " 小时 " + mnum + " 分 " + snum + " 秒.";
    }
setInterval("createtime()",250);
</script>

        
<div class="busuanzi-count">
  <script async src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>

  
    <span class="site-uv">
      <i class="fa fa-user"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_uv"></span>
      
    </span>
  

  
    <span class="site-pv">
      <i class="fa fa-eye"></i>
      <span class="busuanzi-value" id="busuanzi_value_site_pv"></span>
      
    </span>
  
</div>








        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  












  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  

  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  

</body>
</html>
